/*--------------------------------------------------------------------------
    This source file is part of Abraxas Game Engine
    For the latest info, see http://code.google.com/abraxasge/
    Copyright (C) 2009, Andrew LeRoy <aleroy@member.igda.org>
    ---

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA  02110-1301, USA.
--------------------------------------------------------------------------*/
#ifndef _Abx_Exception_hpp_
#define _Abx_Exception_hpp_

#include "AbxPrerequisites.hpp"
#include "AbxString.hpp"
#include <exception>


// Check for AbraxasGE assert mode

// RELEASE_EXCEPTIONS mode
#if ABX_ASSERT_MODE == 1
#   ifdef _DEBUG
#       define AbxAssert( a, b ) assert( (a) && (b) )

#   else
#       if ABX_COMP != ABX_COMPILER_BORL
#           define AbxAssert( a, b ) if( !(a) ) ABX_EXCEPT( abx::Exception::ERR_RT_ASSERTION_FAILED, (b), "no function info")
#       else
#           define AbxAssert( a, b ) if( !(a) ) ABX_EXCEPT( abx::Exception::ERR_RT_ASSERTION_FAILED, (b), __FUNC__ )
#       endif

#   endif

// EXCEPTIONS mode
#elif ABX_ASSERT_MODE == 2
#   if ABX_COMP != OGRE_COMPILER_BORL
#       define AbxAssert( a, b ) if( !(a) ) ABX_EXCEPT( abx::Exception::ERR_RT_ASSERTION_FAILED, (b), "no function info")
#   else
#       define AbxAssert( a, b ) if( !(a) ) ABX_EXCEPT( abx::Exception::ERR_RT_ASSERTION_FAILED, (b), __FUNC__ )
#   endif

// STANDARD mode
#else
#   define AbxAssert( a, b ) assert( (a) && (b) )

#endif



namespace Abx
{
    class _AbxExport Exception
    {
      public:
        /**
         * Static error codes.
         * @todo Add more codes.
         */
        enum ExceptionCodes
        {
            ERR_CANNOT_WRITE_TO_FILE,
            ERR_INVALID_STATE,
            ERR_INVALIDPARAMS,
            ERR_RENDERINGAPI_ERROR,
            ERR_DUPLICATE_ITEM,
            ERR_ITEM_NOT_FOUND,
            ERR_FILE_NOT_FOUND,
            ERR_INTERNAL_ERROR,
            ERR_RT_ASSERTION_FAILED, 
            ERR_NOT_IMPLEMENTED
        };
        
      public:
        /**
         * Default constructor.
         */
        Exception( int number, String const& description, String const& source );

        /**
         * Advanced constructor.
         */
        Exception( int number, String const& description, String const& source, const char* type, const char* file, long line );

        /**
         * Copy constructor.
         */
        Exception(Exception const& rhs);

        /// Needed for  compatibility with std::exception
        ~Exception() throw() {}

        /**
         * Assignment operator.
         */
        void operator= (Exception const& rhs);

        /**
         * Returns a string with the full description of this error.
         * @remarks
         *      The description contains the error number, the description
         *      supplied by the thrower, what routine threw the exception,
         *      and will also supply extra platform-specific information
         *      where applicable. For example - in the case of a rendering
         *      library error, the description of the error will include both
         *      the place in which OGRE found the problem, and a text
         *      description from the 3D rendering library, if available.
         */
        virtual String const& getFullDescription(void) const;

        /**
         * Gets the error code.
         */
        virtual int getNumber(void) const throw();

        /**
         * Gets the source function.
         */
        virtual String const& getSource() const { return source; }

        /**
         * Gets source file name.
         */
        virtual String const& getFile() const { return file; }

        /**
         * Gets line number.
         */
        virtual long getLine() const { return line; }

        /**
         * Returns a string with only the 'description' field of this exception. Use 
         * getFullDescriptionto get a full description of the error including line number,
         * error number and what function threw the exception.
         */
        virtual String const& getDescription(void) const { return description; }

        /// Override std::exception::what
        const char* what() const throw() { return getFullDescription().c_str(); }

      private:
        long            line;
        int             number;
        String          typeName;
        String          description;
        String          source;
        String          file;
        mutable String  fullDesc;
    }; // class Exception
    
    /**
     * Template struct which creates a distinct type for each exception code.
     * @note
     *      This is useful because it allows us to create an overloaded method
     *      for returning different exception types by value without ambiguity. 
     *      From 'Modern C++ Design' (Alexandrescu 2001).
     */
    template <int num>
    struct ExceptionCodeType
    {
        enum { number = num };
    };

    // Specialised exceptions allowing each to be caught specifically
    // backwards-compatible since exception codes still used

    class _AbxExport UnimplementedException : public Exception
    {
      public:
        UnimplementedException(int number, String const& description, String const& source, const char* file, long line)
            : Exception(number, description, source, "UnimplementedException", file, line) {}
    };
    class _AbxExport FileNotFoundException : public Exception
    {
      public:
        FileNotFoundException(int number, String const& description, String const& source, const char* file, long line)
            : Exception(number, description, source, "FileNotFoundException", file, line) {}
    };
    class _AbxExport IOException : public Exception
    {
      public:
        IOException(int number, String const& description, String const& source, const char* file, long line)
            : Exception(number, description, source, "IOException", file, line) {}
    };
    class _AbxExport InvalidStateException : public Exception
    {
      public:
        InvalidStateException(int number, String const& description, String const& source, const char* file, long line)
            : Exception(number, description, source, "InvalidStateException", file, line) {}
    };
    class _AbxExport InvalidParametersException : public Exception
    {
      public:
        InvalidParametersException(int number, String const& description, String const& source, const char* file, long line)
            : Exception(number, description, source, "InvalidParametersException", file, line) {}
    };
    class _AbxExport ItemIdentityException : public Exception
    {
      public:
        ItemIdentityException(int number, String const& description, String const& source, const char* file, long line)
            : Exception(number, description, source, "ItemIdentityException", file, line) {}
    };
    class _AbxExport InternalErrorException : public Exception
    {
      public:
        InternalErrorException(int number, String const& description, String const& source, const char* file, long line)
            : Exception(number, description, source, "InternalErrorException", file, line) {}
    };
    class _AbxExport RenderingAPIException : public Exception
    {
      public:
        RenderingAPIException(int number, String const& description, String const& source, const char* file, long line)
            : Exception(number, description, source, "RenderingAPIException", file, line) {}
    };
    class _AbxExport RuntimeAssertionException : public Exception
    {
      public:
        RuntimeAssertionException(int number, String const& description, String const& source, const char* file, long line)
            : Exception(number, description, source, "RuntimeAssertionException", file, line) {}
    };
    
    /**
     * Class implementing dispatch methods in order to construct by-value
     * exceptions of a derived type based just on an exception code.
     * @remarks
     *      This nicely handles construction of derived Exceptions by value (needed
     *      for throwing) without suffering from ambiguity - each code is turned into
     *      a distinct type so that methods can be overloaded. This allows ABX_EXCEPT
     *      to stay small in implementation (desirable since it is embedded) whilst
     *      still performing rich code-to-type mapping. 
     */
    class ExceptionFactory
    {
      private:
        /// Private constructor, no construction
        ExceptionFactory() {}
        
      public:
        static UnimplementedException create(
            ExceptionCodeType<Exception::ERR_NOT_IMPLEMENTED> code, 
            String const& desc, 
            String const& src, const char* file, long line)
        {
            return UnimplementedException(code.number, desc, src, file, line);
        }
        static FileNotFoundException create(
            ExceptionCodeType<Exception::ERR_FILE_NOT_FOUND> code, 
            String const& desc, 
            String const& src, const char* file, long line)
        {
            return FileNotFoundException(code.number, desc, src, file, line);
        }
        static IOException create(
            ExceptionCodeType<Exception::ERR_CANNOT_WRITE_TO_FILE> code, 
            String const& desc, 
            String const& src, const char* file, long line)
        {
            return IOException(code.number, desc, src, file, line);
        }
        static InvalidStateException create(
            ExceptionCodeType<Exception::ERR_INVALID_STATE> code, 
            String const& desc, 
            String const& src, const char* file, long line)
        {
            return InvalidStateException(code.number, desc, src, file, line);
        }
        static InvalidParametersException create(
            ExceptionCodeType<Exception::ERR_INVALIDPARAMS> code, 
            String const& desc, 
            String const& src, const char* file, long line)
        {
            return InvalidParametersException(code.number, desc, src, file, line);
        }
        static ItemIdentityException create(
            ExceptionCodeType<Exception::ERR_ITEM_NOT_FOUND> code, 
            String const& desc, 
            String const& src, const char* file, long line)
        {
            return ItemIdentityException(code.number, desc, src, file, line);
        }
        static ItemIdentityException create(
            ExceptionCodeType<Exception::ERR_DUPLICATE_ITEM> code, 
            String const& desc, 
            String const& src, const char* file, long line)
        {
            return ItemIdentityException(code.number, desc, src, file, line);
        }
        static InternalErrorException create(
            ExceptionCodeType<Exception::ERR_INTERNAL_ERROR> code, 
            String const& desc, 
            String const& src, const char* file, long line)
        {
            return InternalErrorException(code.number, desc, src, file, line);
        }
        static RenderingAPIException create(
            ExceptionCodeType<Exception::ERR_RENDERINGAPI_ERROR> code, 
            String const& desc, 
            String const& src, const char* file, long line)
        {
            return RenderingAPIException(code.number, desc, src, file, line);
        }
        static RuntimeAssertionException create(
            ExceptionCodeType<Exception::ERR_RT_ASSERTION_FAILED> code, 
            String const& desc, 
            String const& src, const char* file, long line)
        {
            return RuntimeAssertionException(code.number, desc, src, file, line);
        }

    };
    
    
    #ifndef ABX_EXCEPT
    #define ABX_EXCEPT(num, desc, src) throw Abx::ExceptionFactory::create( \
        Abx::ExceptionCodeType<num>(), desc, src, __FILE__, __LINE__ );
    #endif
} // namespace Abx

#endif // _Abx_Exception_hpp_
